﻿#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;

namespace JH
{
    /// <summary>
    ///  编辑器注入器
    /// </summary>
    public static class EditorInjector
    {
        public static void Inject()
        {
            // 查找所有标记了InjectAssetAttribute特性的序列化资产，然后找到对应的资产，挂载到当前物体上
            var range = typeof(InjectAssetAttribute)
                .GetObjectMemberInfosByAttributeEm<Object>(true, false, true);
            DoInject(range, InjectAsset);

            // 查找所有标记了InjectAttribute特性的序列化资产，然后找到对应的实例，挂载到当前物体上
            range = typeof(InjectInstanceAttribute)
                .GetObjectMemberInfosByAttributeEm<MonoBehaviour>(true, false, true);
            DoInject(range, InjectInstance);
        }

        /// <summary>
        ///  执行编辑器下的依赖注入，
        /// </summary>
        /// <param name="range">需要处理的范围</param>
        /// <param name="getInstance">获取注入实例的方法</param>
        static void DoInject(Dictionary<Object, HashSet<MemberInfo>> range,
            Func<Object, Type, MemberInfo, Object> getInstance)
        {
            foreach (var entry in range)
            {
                // 获取引用
                var obj = entry.Key;

                // 获取标记的成员
                var members = entry.Value;

                // 遍历成员
                foreach (var eachMember in members)
                {
                    //如果没有标记[SerializeField]，并且不是public,则跳过
                    if (!eachMember.IsDefined(typeof(SerializeField), true)
                        && eachMember is not FieldInfo { IsPublic: true })
                    {
                        Debug.LogWarning($"检测到实例{obj}的成员{eachMember.Name}没有标记[{nameof(SerializeField)}]，" +
                                         $"但标记了{nameof(InjectAssetAttribute)} 特性，" +
                                         "请检查是否需要在编辑器下进行注入！", obj);
                        continue;
                    }

                    // 获取当前成员的类型
                    var memberType = eachMember.MemberType switch
                    {
                        MemberTypes.Field => ((FieldInfo)eachMember).FieldType,
                        MemberTypes.Property => ((PropertyInfo)eachMember).PropertyType,
                        _ => null
                    };
                    if (memberType == null) continue;

                    // 获取需要注入的资产
                    var asset = getInstance(obj, memberType, eachMember);

                    // 通过序列化写入这个实例中
                    var so = new SerializedObject(obj);
                    var property = so.FindProperty(eachMember.Name);
                    if (property != null)
                    {
                        property.objectReferenceValue = asset;
                        so.ApplyModifiedProperties();
                    }
                    else
                    {
                        Debug.LogWarning($"在{obj}中找不到成员{eachMember.Name}，无法注入资产。");
                    }
                }
            }
        }

        /// <summary>
        ///  获取向 target 的成员 eachMember 注入的实例
        /// </summary>
        private static Object InjectInstance(Object target, Type memberType, MemberInfo memberInfo)
        {
            // 获取InjectInstanceAttribute特性
            var injectAttribute = memberInfo.GetCustomAttribute<InjectInstanceAttribute>();
            if (injectAttribute == null) return null;
            var findName = injectAttribute.Name ?? "";

            // 全局查找MonoBehaviour类型的实例
            var findInstance = Object
                .FindObjectsByType(memberType, FindObjectsInactive.Include, FindObjectsSortMode.None)
                .ToArray();
            // 如果找到的实例多余1个通过名称进行筛选
            if (findInstance.Length > 1 && !string.IsNullOrWhiteSpace(findName))
            {
                findInstance = findInstance
                    .Where(instance => instance.name == findName)
                    .ToArray();
            }

            // 如果找到的实例为0
            if (findInstance.Length == 0)
            {
                Debug.LogWarning($"在向{target}注入类型为{memberType.Name}的成员{memberInfo.Name}时，" +
                                 "没有找到匹配的实例，请检查是否有实例存在！", target);
                return null;
            }

            // 如果找到的实例多余1个，抛出警告
            if (findInstance.Length > 1)
                Debug.LogWarning($"在向{target}注入类型为{memberType.Name}的成员{memberInfo.Name}时，" +
                                 "找到多个匹配的实例，请检查是否有重复的实例存在！", target);
            // 使用第一个找到的实例
            return findInstance[0];
        }

        /// <summary>
        ///  获取向 target 的成员 eachMember 注入的资产
        /// </summary>
        private static Object InjectAsset(Object target, Type memberType, MemberInfo eachMember)
        {
            //获取InjectAssetAttribute特性
            var injectAttribute = eachMember.GetCustomAttribute<InjectAssetAttribute>();
            if (injectAttribute == null) return null;
            var findName = injectAttribute.Name ?? "";

            //在编辑器下查找对应类型和名称的资产
            var findGuidList = AssetDatabase.FindAssets($"t:{memberType.Name} {findName}");

            // 如果找到的GUID为0
            if (findGuidList.Length == 0)
            {
                // 可能序列化的类型是MonoBehaviour的子类,这时其实需要挂载预制体,先进行判定
                if (memberType == typeof(MonoBehaviour) ||
                    memberType.IsSubclassOf(typeof(MonoBehaviour)))
                {
                    // 查找预制体
                    findGuidList = AssetDatabase.FindAssets($"t:Prefab {findName}");

                    // 如果还是没有找到，抛出警告
                    if (findGuidList.Length == 0)
                    {
                        Debug.LogWarning($"在向{target}注入类型为{memberType.Name}的成员{eachMember.Name}时，" +
                                         $"没有找到匹配的资产，请检查是否有资产存在！", target);
                        return null;
                    }

                    // 获取全部寻找到的GUID对应的实例,只保留挂载了对应类型脚本的
                    var findAssets = findGuidList
                        .Select(guid =>
                            AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(guid)))
                        .Where(go => go.GetComponent(memberType) != null);

                    // 根据实例重新获取GUID
                    findGuidList = findAssets
                        .Select(go => AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(go)))
                        .ToArray();
                }
            }

            // 如果找到的GUID为0，抛出警告
            if (findGuidList.Length == 0)
            {
                Debug.LogWarning($"在向{target}注入类型为{memberType.Name}的成员{eachMember.Name}时，" +
                                 $"没有找到匹配的资产，请检查是否有资产存在！", target);
                return null;
            }

            // 如果找到的GUID多余1个，抛出警告
            if (findGuidList.Length > 1)
                Debug.LogWarning($"在向{target}注入类型为{memberType.Name}的成员{eachMember.Name}时，" +
                                 $"找到多个匹配的资产，请检查是否有重复的资产存在！", target);

            // 使用第一个找到的GUID
            var guid = findGuidList[0];

            // 获取资产路径
            var path = AssetDatabase.GUIDToAssetPath(guid);

            // 获取资产对象
            return AssetDatabase.LoadAssetAtPath(path, memberType);
        }
    }
}
#endif